package com.zengdada.authority.config.security.intercept;


import com.zengdada.authority.model.AppPower;
import com.zengdada.authority.service.AppRoleAuthorityService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.ServletContext;
import java.util.*;
import java.util.stream.Collectors;


@Service
@Scope("singleton")
public class MyFilterInvocationSecurityMetadataSource implements InitializingBean, FilterInvocationSecurityMetadataSource {

    private PathMatcher matcher;

    @Autowired
    @Qualifier("appRoleAuthorityServiceImpl")
    private AppRoleAuthorityService roleService;
    @Autowired
    private ServletContext servletContext;
    @Value("${authority.application.id}")
    private String APPLICATION_ID;
    @Value("${web.config.authority.user.login.page.url:/authority/user/login.html}")
    private String loginFormUrl;
    @Value("${web.config.authority.no.authority.url:/authority/user/no_authority.html}")
    private String noAuthorityUrl;
    @Value("${web.config.authority.user.login.url:/authority/user/login.do}")
    private String loginUrl;
    @Value("${web.config.authority.user.logout.url:/authority/user/logout.html}")
    private String logoutUrl;
    private String[] default_sources = new String[]{"/error", "/error/**"};
    private static String[] STATIC_RES = {"/**/*.js*", "/**/*.css*", "/**/*.img*", "/**/*.png*", "/**/*.ico*", "/**/*.gif*", "/**/*.ttf*", "/**/*.woff*"};

    private static List<String> staticRes = new ArrayList<>(); // 静态资源
    private static List<String> defaultSources = new ArrayList<>(); // 静态资源
    private static Map<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>();
    private static Map<String, Collection<ConfigAttribute>> defaultMap = new HashMap<String, Collection<ConfigAttribute>>();
    private static Map<String, Collection<ConfigAttribute>> tempmap = new HashMap<String, Collection<ConfigAttribute>>();
    public final static Collection<ConfigAttribute> ROLE_ANONYMOUS = new ArrayList<ConfigAttribute>() {{
        add(new SecurityConfig("ROLE_ANONYMOUS"));
    }};
    public final static Collection<ConfigAttribute> ROLE_AUTHORITY_USER = new ArrayList<ConfigAttribute>() {{
        add(new SecurityConfig("ROLE_AUTHORITY_USER"));
    }};

//    404, 403, 500, 501

    /*
     * 项目启动时
     * 初始化用户权限，为了简便操作没有从数据库获取
     * 实际操作可以从数据库中获取所有资源路径url所对应的权限
     */
    public void afterPropertiesSet() throws Exception {
        this.matcher = new AntPathMatcher();//用来匹配访问资源路径
        defaultSources.addAll(Arrays.stream(default_sources).collect(Collectors.toList()));
        defaultSources.addAll(Arrays.asList(loginFormUrl, noAuthorityUrl, loginUrl, logoutUrl));
        staticRes.addAll(Arrays.stream(STATIC_RES).collect(Collectors.toList()));
        initOrUpdateMap();
        //        ServletContext servletContext = ContextLoader.getCurrentWebApplicationContext().getServletContext();
    }

    //    @PostConstruct//  被@PostConstruct修饰的方法会在服务器加载Servle的时候运行，并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行
    public synchronized Long initOrUpdateMap() {
        List<Map<String, Object>> resources = roleService.selectAllResource(APPLICATION_ID);
        List<AppPower> notResources = roleService.selectAllNotResource(APPLICATION_ID);
        for (String default_source : defaultSources) {
            if (StringUtils.isBlank(default_source) || tempmap.containsKey(default_source)) continue;
            tempmap.put(default_source, ROLE_ANONYMOUS);
        }
//        defaultSources.addAll(notResources.stream().map(e -> e.getUrl()).collect(Collectors.toList()));
        notResources.forEach(power -> {
            if (power.getUrl() == null || tempmap.containsKey(power.getUrl())) return;
            tempmap.put(power.getUrl(), ROLE_ANONYMOUS);
        });
        defaultMap.clear();
        defaultMap.putAll(tempmap);
        tempmap.clear();
        resources.forEach(power -> {
            String resource = String.valueOf(power.get("url"));
            if (StringUtils.isNoneBlank(resource)) {
                Collection<ConfigAttribute> atts = tempmap.containsKey(resource) ? tempmap.get(resource) : new ArrayList<ConfigAttribute>();
                SecurityConfig temp = new SecurityConfig(String.valueOf(power.get("code")));
                if (!atts.contains(temp)) atts.add(temp); //最基本
                tempmap.put(resource, atts);
            }
        });
        map.clear();
        map.putAll(tempmap);
        tempmap.clear();
        // 缓存权限版本号
        long version = new Date().getTime();
        servletContext.setAttribute("security_power_version", version);
        return version;
    }

    /**
     * 请求访问时
     *
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object)
            throws IllegalArgumentException {
        // TODO Auto-generated method stub
        FilterInvocation filterInvocation = (FilterInvocation) object;

//        String requestURI = filterInvocation.getRequestUrl();
        String URI = filterInvocation.getHttpRequest().getServletPath();
        //循环资源路径，当访问的Url和资源路径url匹配时，返回该Url所需要的权限
//        for (Iterator<Map.Entry<String, Collection<ConfigAttribute>>> iter = map.entrySet().iterator(); iter.hasNext(); ) {
//            Map.Entry<String, Collection<ConfigAttribute>> entry = iter.next();
//            String url = entry.getKey();
//
//            if (matcher.match(url, requestURI)) {
//                return map.get(url);
//            }
//        }
        for (String s : staticRes) {
            if (matcher.match(s, URI)) return null;
        }
        for (Map.Entry<String, Collection<ConfigAttribute>> entry : defaultMap
                .entrySet()) {
            if (matcher.match(entry.getKey(), URI)) return entry.getValue();
        }
        for (Map.Entry<String, Collection<ConfigAttribute>> entry : map
                .entrySet()) {
            if (matcher.match(entry.getKey(), filterInvocation.getRequestUrl())) return entry.getValue();
            if (matcher.match(entry.getKey(), URI)) return entry.getValue();
        }
        return ROLE_AUTHORITY_USER;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        // TODO Auto-generated method stub
        return null;
    }

    /* (non-Javadoc)
     * @see org.springframework.security.intercept.ObjectDefinitionSource#getConfigAttributeDefinitions()
     */
    @SuppressWarnings("rawtypes")
    public Collection getConfigAttributeDefinitions() {
        return null;
    }

    /* (non-Javadoc)
     * @see org.springframework.security.intercept.ObjectDefinitionSource#supports(java.lang.Class)
     */
    public boolean supports(@SuppressWarnings("rawtypes") Class clazz) {
        return true;
    }

    /**
     * @param filterInvocation
     * @return
     */
    @SuppressWarnings("unchecked")
    private Map<String, String> getUrlAuthorities(org.springframework.security.web.FilterInvocation filterInvocation) {
        ServletContext servletContext = filterInvocation.getHttpRequest().getSession().getServletContext();
        return (Map<String, String>) servletContext.getAttribute("urlAuthorities");
    }

}
